package me.leoray.leilog;

import android.os.Environment;
import android.text.TextUtils;
import android.util.Log;

import java.io.BufferedWriter;
import java.io.File;
import java.io.FileFilter;
import java.io.IOException;
import java.io.PrintWriter;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Arrays;
import java.util.Comparator;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.Random;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

/**
 * 日志输出到文件
 * 注意: 一个{@link FilePipeLine}和一个logDir对应
 * Created by leoraylei on 16/8/27.
 */
public class FilePipeLine extends AbstractPipeLine {

    public static final String TAG = "FilePipeLine";
    public static final String LOG_FILE_SUFFIX = ".log"; //默认文件存储后缀

    private boolean saveLog = false;   //是否需要存储日志
    private String logDir;  //日志文件根目录
    private int logFileLeave = 3; //历史日志文件保留个数
    private int logFileSize = 1024 * 1024 * 3;    //单个日志文件的大小，默认3M

    private ExecutorService executors;

    private FilePipeLine() {
        executors = Executors.newSingleThreadExecutor(); //单线程池保证写文件的顺序性
        level = Log.ERROR; //默认的文件存储级别
    }

    private static Map<String, FilePipeLine> filePipeLineMap = new HashMap<>();

    /**
     * 必须指定一个日志存储目录且不可更改
     *
     * @param logDir
     * @return
     */
    public static FilePipeLine pipeLine(String logDir) {
        if (logDir == null) {
            return null;
        }
        if (filePipeLineMap.containsKey(logDir)) {
            return filePipeLineMap.get(logDir);
        }
        FilePipeLine pipeLine = new FilePipeLine();
        pipeLine.logDir = logDir;
        filePipeLineMap.put(logDir, pipeLine);
        return pipeLine;
    }

    /**
     * 设置单个日志文件大小
     *
     * @param logFileSize 单位：字节
     */
    public FilePipeLine fileSize(int logFileSize) {
        this.logFileSize = logFileSize;
        return this;
    }

    /**
     * 设置本地最多保留多少个日志文件
     *
     * @param logFileLeave 0<logFileLeave<=10
     */
    public FilePipeLine fileLeave(int logFileLeave) {
        if (logFileLeave < 0) {
            logFileLeave = 3;
        }
        this.logFileLeave = logFileLeave;
        return this;
    }

    /**
     * 设置文件存储最低日志级别
     * 等价于{@link #threshold()}
     *
     * @param level
     */
    @Override
    public FilePipeLine level(int level) {
        super.level(level);
        return this;
    }

    /**
     * 什么级别的日志需要存储文件
     *
     * @param saveFileThreshold 参考{@link Log} 中的日志级别
     */
    public FilePipeLine threshold(int saveFileThreshold) {
        level(saveFileThreshold);
        return this;
    }

    @Override
    public FilePipeLine setLogProxy(LogProxy proxy) {
        super.setLogProxy(proxy);
        return this;
    }

    public boolean isSaveLog() {
        return saveLog;
    }


    public int logFileSize() {
        return logFileSize;
    }

    public int logFileLeave() {
        return logFileLeave;
    }

    public String logDir() {
        return logDir;
    }

    public int threshold() {
        return level;
    }

    //单线程下使用
    private static DateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd_HH-mm-ss.SSS");

    @Override
    protected void log(final int logType, final String tag, final String message, final Throwable t) {
        executors.submit(new Runnable() {
            @Override
            public void run() {
                StringBuilder builder = new StringBuilder();
                if (level <= logType) {
                    //The log will be written in the log file.
                    builder.append(dateFormat.format(new Date()));
                    builder.append("    ");
                    builder.append(getTypeName(logType));
                    builder.append("    ");
                    builder.append(tag);
                    builder.append("    ");
                    builder.append(message);
                    if (t != null) {
                        builder.append(System.getProperty("line.separator"));
                        builder.append(android.util.Log.getStackTraceString(t));
                    }
                    write2File(builder.toString());
                }
            }
        });
    }

    public static String getTypeName(int type) {
        switch (type) {
            case Log.VERBOSE:
                return "V";
            case Log.DEBUG:
                return "D";
            case Log.INFO:
                return "I";
            case Log.WARN:
                return "W";
            case Log.ERROR:
                return "E";
            case Log.ASSERT:
                return "A";
            default:
                return "LOG";
        }
    }

    private void write2File(String message) {
        PrintWriter pw = null;
        File file = getLogFile();
        if (file == null) {
            Log.e(TAG, "没有配置日志根目录.");
            return;
        }
        try {
            deleteCount++;
            pw = new PrintWriter(new BufferedWriter(new java.io.FileWriter(file, true)));
            pw.println(message);
            pw.flush();

            if (file.length() > logFileSize() && deleteCount % 20 == 0) { //提前准备好新的日志文件
                deleteCount = 0;
                createNewLogFile();
            }
        } catch (IOException e) {
            Log.e(TAG, e.getMessage());
        } finally {
            if (pw != null) {
                pw.close();
            }
        }
    }

    /**
     * 静态工具方法,得到一个默认的log日志位置，规则如下：
     * 如果有SD卡且可用，则是用外置SD卡的默认应用目录作为日志文件夹的根目录
     *
     * @param context    当前应用的context
     * @param logDirName 是日志文件夹的名称，不是绝对路径
     * @return
     */
    public static String logDir(android.content.Context context, String logDirName) {
        if (context == null) {
            return null;
        }
        File logDirFile;
        if (Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) {
            logDirFile = context.getExternalFilesDir(logDirName);
        } else {
            String dir = context.getFilesDir().getAbsolutePath() + File.separator + logDirName;
            logDirFile = new File(dir);
        }
        if (!logDirFile.exists()) {
            logDirFile.mkdirs();
        }
        Log.i(TAG, "默认日志存储目录:" + logDirFile.getAbsolutePath());
        return logDirFile.getAbsolutePath();
    }

    /**
     * 得到日志文件
     * 读取{@link #logDir}目录，找到最新的日志文件并返回，如果没有则创建
     *
     * @return
     */
    private File getLogFile() {
        String logDir = logDir();
        if (TextUtils.isEmpty(logDir))
            return null;
        File dir = new File(logDir);
        if (dir == null) {
            return null;
        }
        if (!dir.exists()) {
            dir.mkdirs();
        } else if (!dir.isDirectory()) {
            return null;
        }
        File[] files = listLogFiles(dir);
        if (files == null || files.length <= 0) {
            return createNewLogFile();
        }
        File newest = files[0];
        for (File file : files) {
            if (file.lastModified() > newest.lastModified()) {
                newest = file;
            }
        }
        return newest;
    }

    /**
     * 创建新日志文件
     *
     * @return
     */
    protected File createNewLogFile() {
        String file = String.format("%s%s%s", logDir(), File.separator, randomFileName());
        File f = new File(file);
        if (!f.exists()) {
            try {
                f.createNewFile();
            } catch (IOException e) {
                e.printStackTrace();
                Log.e(TAG, "创建新的日志文件失败:" + e.getMessage());
            }
        }
        if (!f.canWrite()) {
            Log.e(TAG, "没有读写权限，无法写日志.");
            return null;
        }
        deleteOldFiles();
        return f;
    }


    /**
     * 列出日志目录下的所有日志文件
     *
     * @param dir
     * @return
     */
    private static File[] listLogFiles(File dir) {
        return dir.listFiles(new FileFilter() {
            @Override
            public boolean accept(File file) {
                return file.getName().endsWith(LOG_FILE_SUFFIX) && file.isFile();
            }
        });
    }

    private void deleteOldFiles() {
        File[] files = listLogFiles(new File(logDir()));
        if (files.length <= logFileLeave()) {   //不需要删除文件
            return;
        }
        //按时间倒序
        Arrays.sort(files, new Comparator<File>() {
            @Override
            public int compare(File lhs, File rhs) {
                if (lhs.lastModified() > rhs.lastModified()) {
                    return 1;
                } else if (lhs.lastModified() < rhs.lastModified()) {
                    return -1;
                } else {
                    return 0;
                }
            }
        });
        for (int i = files.length - 1; i >= logFileLeave(); i--) {
            files[i].delete();
        }
    }

    /**
     * 生成信息的日志文件名称，规则：
     * 当前日期字符串_随机六位数字.log
     *
     * @return
     */
    private static String randomFileName() {
        return new SimpleDateFormat("yyyyMMddHHmmssSSS").format(new Date()) + (100000 + new Random().nextInt(900000)) + LOG_FILE_SUFFIX;
    }


    private long deleteCount = 0; //temp


}
